/*
 *  *
 * Copyright (C) 2007 lufang <lufang@gmail.com>
 *
 * Version 1.0 june 2007
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>

#define LOG_TAG "MODEM_USB"
#include <utils/Log.h>

#include "ril_util.h"
#include "modem_usb.h"
#include "usb.h"
#include "atchannel.h"
#include "at_tok.h"

#ifdef PLATFORM_A10
	#define USB_POWER_OFF		"auto"
	#define USB_POWER_ON		"on"

#else
	#define USB_POWER_OFF		"suspend"
	#define USB_POWER_ON		"auto"
#endif

#define 	MODEM_PORT_CONFIG_FILE	"/system/etc/modem_port.conf"
#define 	MAX_PORT_CONFIG	256
#define	 	LINE_DIM 256

typedef struct _ModemPortConfigTable
{
	int		vendor_id;
	int		product_id;
	int		at_port;
	int		data_port;
	NetStandard network_type;
}ModemPortConfigTable;

struct _ModemUsb
{
	char	path[MAX_PATH_LEN + 1];

	int		product_id;
	int		vendor_id;
};

static ModemPortConfigTable* s_modem_configs = NULL;
/*
{
	{0x19f5, 0x9909, 3, 1, NET_CDMA},
	{0x19f5, 0x9013, 2, 3, NET_GSM},
	{0x12d1, 0x1446, 0, 0, NET_MODEM_SWITCH},
	{0x12d1, 0x1001, 0, 2, NET_CDMA},
	{0, 0, 0, 0, 0}
};
*/

static const char * s_device_path = NULL;

static int modem_usb_search_path(ModemUsb* thiz)
{
	usb_init();
	usb_find_busses();
	usb_find_devices();

	struct usb_bus* bus;
	struct usb_device* dev;
#ifdef ENABLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	for(bus=usb_get_busses(); bus; bus = bus->next)
	{
		for(dev=bus->devices; dev; dev=dev->next)
		{
			ALOGD("%s: usb device found, vendor id: %x, product id: %x", __func__, dev->descriptor.idVendor, dev->descriptor.idProduct);

			if(dev->descriptor.idVendor == thiz->vendor_id
					&& dev->descriptor.idProduct == thiz->product_id)
			{
				//modem usb device found.	
				int devnum = (int)strtol(dev->filename, NULL, 10); 
				int busnum = (int)strtol(dev->bus->dirname, NULL, 10);

				snprintf(thiz->path, MAX_PATH_LEN, "/sys/class/usb_device/usbdev%d.%d/device", busnum, devnum);

				if(access(thiz->path, F_OK) != 0)
				{
					memset(thiz->path, 0, MAX_PATH_LEN);
					continue;
				}

				ALOGD("%s: path=%s", __func__, thiz->path);

				return 1;
			}
		}
	}

	ALOGD("%s: search failed.", __func__);

	return 0;
}

static void modem_usb_get_power_device(ModemUsb* thiz, char* path)
{
#ifdef ENABLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	modem_usb_search_path(thiz);
#ifdef PLATFORM_A10
	snprintf(path, MAX_PATH_LEN, "%s/power/control", thiz->path);
#else
	snprintf(path, MAX_PATH_LEN, "%s/power/level", thiz->path);
#endif

	return;
}

ModemUsb* 	modem_usb_create(int vendor_id, int product_id)
{
	ModemUsb* thiz = calloc(1, sizeof(ModemUsb));
#ifdef ENABLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	if(thiz == NULL)
	{
		return NULL;
	}

	thiz->vendor_id = vendor_id;
	thiz->product_id = product_id;
	ALOGD("%s: vendor id: %x, product id: %x", __func__, vendor_id, product_id);

	return thiz;
}

void modem_usb_suspend(ModemUsb* thiz)
{
	if(thiz == NULL) return;

	char usb_power_file[MAX_PATH_LEN + 1];
	int fd = 0;

#ifdef ENABLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	modem_usb_get_power_device(thiz, usb_power_file);
	fd = open(usb_power_file, O_RDWR);
	if(fd > 0) {
		write(fd, USB_POWER_OFF, strlen(USB_POWER_OFF));
		sleep(1);
		close(fd);

		ALOGD("%s: usb_power file: %s",__func__, usb_power_file);

		return;
	}

	ALOGD("%s: usb_power_file open failed: %s", __func__, strerror(errno));

	return;
}

void modem_usb_resume(ModemUsb* thiz)
{
	if(thiz == NULL) return;
	
	char usb_power_file[MAX_PATH_LEN + 1];
	int fd = 0;
#ifdef ENABLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	modem_usb_get_power_device(thiz, usb_power_file);
	fd = open(usb_power_file, O_RDWR);

	if(fd > 0) {
		write(fd, USB_POWER_ON, strlen(USB_POWER_ON));
		sleep(3);
		close(fd);

		ALOGD("%s: usb_power file: %s",__func__, usb_power_file);
		return;
	}

	ALOGD("%s: usb_power_file open failed: %s", __func__, strerror(errno));

	return;
}

void modem_usb_reset(ModemUsb* thiz)
{
	ALOGD("%s %p\n", __func__, thiz);
	if(thiz == NULL) return;
	
	char usb_power_file[MAX_PATH_LEN + 1];
	int fd = 0;

#ifdef ENABLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	modem_usb_get_power_device(thiz, usb_power_file);
	fd = open(usb_power_file, O_RDWR);

	if(fd > 0) {
		write(fd, USB_POWER_OFF, strlen(USB_POWER_OFF));
		sleep(2);
		write(fd, USB_POWER_ON, strlen(USB_POWER_ON));
		sleep(5);
		close(fd);

		ALOGD("%s: usb_power file: %s",__func__, usb_power_file);

		return;
	}

	ALOGD("%s: usb_power_file open failed: %s", __func__, strerror(errno));

	return;
}

int modem_usb_exist(ModemUsb* thiz)
{
	if(thiz == NULL) return 0;
	
	ALOGD("%s: \n", __func__);
	if(modem_usb_search_path(thiz))
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

static const int modem_usb_try_get_real_index(const char* prefix, const int index)
{
	int ret = -1;
	FILE* fp = NULL;
	char device_uevent_file[64];
	int	device_index = 0;
	int i = 0;

	do 
	{
		snprintf(device_uevent_file, 64, MODEM_USBSERIAL_UEVENT_FILE, prefix, i);
		if((fp = fopen(device_uevent_file, "r")) != NULL)
		{
			char* line = NULL;
			int len = 0;
			int read = 0;

			while ((read = modem_getline(&line, &len, fp)) > 0) 
			{
				if(strStartsWith(line, "MINOR="))
				{
					device_index++;
					break;
				}
			}

			if (line != NULL)
			{
				free(line);
			}
			fclose(fp);

			if(device_index == (index + 1))
			{
				ret = i;
				break;
			}
			else if(device_index >  MODEM_USBSERIAL_MAX_INDEX)
			{
				break;
			}
		}
		else
		{
			//ALOGD("%s: can't open usb %d", __func__, i);
		}
		i++;
		if(i > MODEM_USBSERIAL_MAX_VALID_INDEX)
		{
			break;
		}
	}while(1);

	return ret;
}

int modem_usb_get_port_path(ModemUsb* thiz, int index, char* path) 
{
	return modem_usb_get_port_path_by_prefix(thiz, "ttyUSB", index, path);
}

int modem_usb_get_port_path_by_prefix(ModemUsb* thiz, const char* prefix, int index, char* path) 
{
	int ri = -1;

	if((ri = modem_usb_try_get_real_index(prefix, index)) != -1) 
	{
		sprintf(path, "/dev/%s%d", prefix, ri);
	}
#ifdef ENABLE_LOG
	ALOGD("%s reflact /dev/%s%d to /dev/%s%d: \n", 
			__func__, prefix, index, prefix, ri);
#endif

	return ri != -1;
}

void modem_usb_destroy(ModemUsb* thiz)
{
#ifdef ENABLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	free(thiz);

	return;
}

static void dump_modem_port_config()
{
	int i = 0;
#ifdef ENABLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	for(i = 0; s_modem_configs[i].vendor_id != 0; i++)
	{
		ALOGD("vendor_id=%x,product_id=%x,at_port=%d,data_port=%d,network_type=%d\n",
			s_modem_configs[i].vendor_id, s_modem_configs[i].product_id, 
			s_modem_configs[i].at_port, s_modem_configs[i].data_port,
			s_modem_configs[i].network_type);
	}
}


static void modem_port_config_init()
{
	FILE* fp = NULL;
	char str[LINE_DIM];
	int i = 0;
#ifdef ENABLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	if(s_modem_configs != NULL)
	{
		return;
	}
	
	s_modem_configs = calloc(1, sizeof(ModemPortConfigTable) * MAX_PORT_CONFIG);
	if((fp = fopen(MODEM_PORT_CONFIG_FILE, "r")) != NULL)
	{
		while (fgets(str, LINE_DIM-1, fp) != NULL) {
			if(str[0] != '\n')
			{
				int match = 0;

				match = sscanf(str, "%x,%x,%d,%d,%d", &s_modem_configs[i].vendor_id, 
					&s_modem_configs[i].product_id, &s_modem_configs[i].at_port, 
					&s_modem_configs[i].data_port, (int*)(&s_modem_configs[i].network_type));
				if(match == 5)
				{
					i++;
				}
				else
				{
					s_modem_configs[i].vendor_id = 0;
					s_modem_configs[i].product_id = 0;
					s_modem_configs[i].at_port = 0;
					s_modem_configs[i].data_port = 0;
					s_modem_configs[i].network_type = 0;
				}
			}
		}
		fclose(fp);
	}
	dump_modem_port_config();

	return;
}
static int modem_get_port_config(int vendor_id, int product_id, ModemPortConfig* config)
{
	int ret = 0;
	int i = 0;
#ifdef ENABLE_LOG	
	ALOGD("%s: \n", __func__);
#endif
	if(config == NULL)
	{
		return 0;
	}
	
	modem_port_config_init();
	if(s_modem_configs == NULL)
	{
		return 0;
	}

	for(i = 0; s_modem_configs[i].vendor_id != 0; i++)
	{
		if(s_modem_configs[i].vendor_id == vendor_id && s_modem_configs[i].product_id == product_id)
		{
			config->at_port_index = s_modem_configs[i].at_port;
			config->data_port_index = s_modem_configs[i].data_port;
			config->network_type = s_modem_configs[i].network_type;
			ret = 1;
			break;
		}
	}

	return ret;
}

int	modem_usb_get_port_config(ModemUsb* thiz, ModemPortConfig* config)
{
#ifdef ENABLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	if(thiz != NULL && config != NULL)
	{
		return modem_get_port_config(thiz->vendor_id, thiz->product_id, config);
	}

	return 0;
}

int modem_getline(char** line, int* len, FILE* fp)
{
	static char* buffer = NULL;
	static int buf_len = 0;
	static int read_pos = 0;
	int line_len = 0;
	int i = 0;

#ifdef ENABLE_LOG
	//ALOGD("%s: \n", __func__);
#endif
	if(*line == NULL)
	{
		if(buffer != NULL)
		{
			free(buffer);
			buffer = NULL;
		}
		buf_len = 0;
		read_pos = 0;
	}
	
	if(buffer == NULL)
	{
		fseek(fp, 0, SEEK_END);
		buf_len = ftell(fp);
		fseek(fp, 0, SEEK_SET);
		buffer = calloc(1, buf_len);
		fread(buffer, buf_len, 1, fp);
	}

	while(buffer[read_pos] == '\r' || buffer[read_pos] == '\n')
	{
		read_pos++;
	}

	for(i = read_pos; i < buf_len; i++)
	{
		if(buffer[i] == '\r' || buffer[i] == '\n')
		{
			break;
		}
	}
	

	line_len = i - read_pos;
	if(*len < line_len)
	{
		*line = realloc(*line, line_len);
		*len = line_len;
	}
	memcpy(*line, buffer + read_pos, line_len);
	read_pos += line_len;

	return line_len;
}

int modem_usb_init()
{
#ifdef ENABLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	modem_port_config_init();
#ifdef SUPPORT_EXTERNAL_CONFIG
	create_external_dongle_config();
#endif

	return 1;
}

int modem_usb_uninit()
{
#ifdef ENABLE_LOG
	ALOGD("%s: \n", __func__);
#endif
#ifdef SUPPORT_EXTERNAL_CONFIG
	destroy_external_dongle_config();
#endif

	return 1;
}

